﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Text.RegularExpressions;
using System.Web.UI.WebControls;
using Medianamik.Core.CodeGeneration;
using Medianamik.Globalization;

namespace Medianamik.Core.Synchronization
{
    //Attention : A chaque fois qu'une méthode AddToUniquely est appelée
    //la synchronistion se pousuit (pour indiquer toutes les erreurs)
    //mais aucune sauvegarde (à la BD) ne sera effectuée

    public class NodeTypeSynchronizer : MedianamikSynchronizer<RecordType, NodeType>
    {
        // todo centraliser / améliorer / vérifier l'utilite
        public void SaveAll(IEnumerable<NodeType> items)
        {
            //1 seule transaction
            //NodeTypeManager.SaveTypes<NodeType>(items);

            //TODO: Il faut réordonner les types pour respecter le fk suivantes :
            //ParentTypeId
            //PrimaryTypeId et PrimaryPropertyId des propriétés
            //Si ces données n'existe pas déjà dans la BD ça brise
            var orderedIems = new List<NodeType>(items);

            var codeService = new CodeService();


            //Désactiver les FOREIGN KEY Constraints
            //sur la table medianamik_Property

            codeService.DisableTableConstraint("medianamik_Property", "FK_medianamik_Property_medianamik_Type1");
            codeService.DisableTableConstraint("medianamik_Property", "FK_medianamik_Property_medianamik_Relation");

            for (var i = 0; i < orderedIems.Count(); i++)
            {
                NodeTypeManager.SaveType(orderedIems.ElementAt(i));
            }

            //Regénérer TypesToRegenerate
            foreach (var t in TypesToRegenerate)
            {
                codeService.GenerateView(t);
            }

            //Clean TypesToClean
            foreach (var t in TypesToClean)
            {
                codeService.SwitchToNonVersionableTypeMaintenance(t);
            }

            //Traductions
            Translations.ForEach(Translator.CreateResource);

            try
            {
                codeService.EnableTableConstraint("medianamik_Property", "FK_medianamik_Property_medianamik_Type1");
            }
            catch (Exception)
            {
                AddException("FK_medianamik_Property_medianamik_Type1 n'est plus respectée.");
            }

            try
            {
                codeService.EnableTableConstraint("medianamik_Property", "FK_medianamik_Property_medianamik_Relation");
            }
            catch (Exception)
            {
                AddException("FK_medianamik_Property_medianamik_Relation n'est plus respectée.");
            }
        }

        protected override NodeType GetTResult(RecordType source)
        {
            NodeType type = null;

            if (source.Id.HasValue)
                type = NodeTypeManager.GetNodeType(source.Id.Value);
            else if (source.Name != null)
                type = NodeTypeManager.GetNodeType(source.Name);

            return type;
        }

        protected override void FinalizeSynchronization()
        {
            NodeTypeManager.Provider.Reload();
        }

        //Important : Les business rules doivent avoir été validées avant d'arriver ici
        //Important : On ne fait plus de validation rendu ici

        protected override NodeType NewTResult(RecordType source)
        {
            var newType = source.Convert(Translations);

            newType.Status = EntityStates.New;

            AddLog(String.Format("Le type ({0}/{1}) a été ajouté.",
                newType.ID, newType.Name));

            return newType;
        }

        protected override bool UpdateTResult(RecordType source, NodeType existingType)
        {
            bool hasChanged = false;

            if (existingType.Name != source.Name)
            {
                AddLog(String.Format("Le nom du type ({0}/{1}) a été modifié ({2}).",
                existingType.ID, existingType.Name, source.Name));
                existingType.Name = source.Name;
                hasChanged = true;
            }

            if (existingType.Description != source.Description)
            {
                AddLog(String.Format("La description du type ({0}/{1}) a été modifiée.",
                existingType.ID, existingType.Name));
                existingType.Description = source.Description;
                hasChanged = true;
            }

            if (existingType.IsInheritable != source.IsInheritable)
            {
                if (existingType.IsInheritable)
                    AddLog(String.Format("Le type ({0}/{1}) n'est plus \"héritable\".",
                        existingType.ID, existingType.Name));
                else
                    AddLog(String.Format("Le type ({0}/{1}) est désormais \"héritable\".",
                        existingType.ID, existingType.Name));

                existingType.IsInheritable = source.IsInheritable;
                hasChanged = true;
            }

            if (existingType.IsGenerated != source.IsGenerated)
            {
                if (existingType.IsGenerated)
                    AddLog(String.Format("Le type ({0}/{1}) n'est plus généré.",
                        existingType.ID, existingType.Name));
                else
                    AddLog(String.Format("Le type ({0}/{1}) est désormais généré.",
                        existingType.ID, existingType.Name));

                existingType.IsGenerated = source.IsGenerated;
                hasChanged = true;
            }

            if (existingType.IsAbstract != source.IsAbstract)
            {
                if (existingType.IsAbstract)
                    AddLog(String.Format("Le type ({0}/{1}) n'est plus abstrait.",
                        existingType.ID, existingType.Name));
                else
                    AddLog(String.Format("Le type ({0}/{1}) est désormais abstrait.",
                        existingType.ID, existingType.Name));

                existingType.IsAbstract = source.IsAbstract;
                hasChanged = true;
            }

            if (existingType.IsSystem != source.IsSystem)
            {
                if (existingType.IsSystem)
                    AddLog(String.Format("Le type ({0}/{1}) n'est plus \"système\".",
                        existingType.ID, existingType.Name));
                else
                    AddLog(String.Format("Le type ({0}/{1}) est désormais \"système\".",
                        existingType.ID, existingType.Name));

                existingType.IsSystem = source.IsSystem;
                hasChanged = true;
            }

            if (existingType.IsManyToMany != source.IsManyToMany)
            {
                AddException(String.Format("La propriété IsManyToMany du type ({0}/{1}) a été modifiée.",
                    existingType.ID, existingType.Name));
                //existingType.IsManyToMany = source.IsManyToMany;
                //hasChanged = true;
            }

            if (existingType.Indexable != source.IsIndexable)
            {
                if (existingType.Indexable)
                    AddLog(String.Format("Le type ({0}/{1}) n'est plus indexé.",
                        existingType.ID, existingType.Name));
                else
                    AddLog(String.Format("Le type ({0}/{1}) est désormais indexé.",
                        existingType.ID, existingType.Name));

                existingType.Indexable = source.IsIndexable;
                hasChanged = true;
            }

            if (existingType.Listable != source.IsListable)
            {
                if (existingType.Listable)
                    AddLog(String.Format("Le type ({0}/{1}) n'est plus listable.",
                        existingType.ID, existingType.Name));
                else
                    AddLog(String.Format("Le type ({0}/{1}) est désormais listable.",
                        existingType.ID, existingType.Name));

                existingType.Listable = source.IsListable;
                hasChanged = true;
            }

            if (existingType.ExtendedProperties.Document.ToString() != source.ExtendedProperties)
            {
                AddLog(String.Format("La propriété ExtendedProperties du type ({0}/{1}) a été modifiée.",
                    existingType.ID, existingType.Name));

                existingType.ExtendedProperties = new NodeTypeExtendedProperties(source.ExtendedProperties);
                hasChanged = true;
            }

            if (existingType.IsVersioned != source.IsVersioned)
            {
                //On a coché/décoché "Est Versionable"
                //Il faut regénérer la vue liée au Type et les vues de tous les types parents
                //On conserve les types qu'on doit générés afin de le faire lors
                //du SaveTypes et ne pas le refaire plusieurs fois pour les mêmes types parents
                ////Et supprimer le vieux Data si on passe le type en NonVersionable

                if (existingType.IsVersioned)
                {
                    var msg = String.Format("Le type ({0}/{1}) n'est plus \"versionable\".",
                                            existingType.ID, existingType.Name);
                    AddLog(msg);
                    AddWarning(msg);
                }
                else
                {
                    var msg = String.Format("Le type ({0}/{1}) est désormais \"versionable\".",
                                            existingType.ID, existingType.Name);
                    AddLog(msg);
                    AddWarning(msg);

                    TypesToClean.Add(existingType);
                }

                existingType.IsVersioned = source.IsVersioned;
                hasChanged = true;

                NodeTypeManager.GetParentTypes(existingType.ID, true).ForEach(t => TypesToRegenerate.Add(t));
            }

            var legitimateParentTypes = source.LegitimateParentRecordTypeIds
                .Select(rt => new NodeType(rt));
            if (!existingType.OriginalLegitimateParentTypes.SequenceEqual(legitimateParentTypes))
            {
                AddLog(String.Format("Les LegitimateParentTypes du type ({0}/{1}) ont été modifiés.",
                    existingType.ID, existingType.Name));

                existingType.LegitimateParentTypes.Clear();
                existingType.LegitimateParentTypes.AddRange(legitimateParentTypes);
                hasChanged = true;
            }

            var legitimateParentNodes = source.LegitimateParentRecordIds
                .Select(r => new Node(r));
            if (!existingType.OriginalLegitimateParentNodes.SequenceEqual(legitimateParentNodes))
            {
                AddLog(String.Format("Les LegitimateParentNodes du type ({0}/{1}) ont été modifiés.",
                    existingType.ID, existingType.Name));

                existingType.LegitimateParentNodes.Clear();
                existingType.LegitimateParentNodes.AddRange(legitimateParentNodes);
                hasChanged = true;
            }

            var templates = source.Templates.Select(rtt =>
                new TypeTemplate()
                    {
                        TemplateId = rtt.TemplateId,
                        NodeTypeId = source.Id,
                        WebSiteId = rtt.WebSiteId
                    });

            if (!existingType.Templates.SequenceEqual(templates))
            {
                AddLog(String.Format("Les Templates du type ({0}/{1}) ont été modifiés.",
                    existingType.ID, existingType.Name));

                existingType.Templates.Clear();
                existingType.Templates.AddRange(templates);
                hasChanged = true;
            }

            //Mettre les propriétés à jour (ajout/suppression/modification)
            foreach (var sourceProperty in source.Properties)
            {
                var existingProperty = existingType.Properties.FirstOrDefault(prop => prop.ID.Equals(sourceProperty.Id));

                if (existingProperty == null) //Ajout
                {
                    AddLog(String.Format("La propriété ({0}/{1}) a été ajoutée au type ({2}/{3}).",
                    sourceProperty.Id, sourceProperty.Name, existingType.ID, existingType.Name));

                    hasChanged = true;
                    sourceProperty.AddToType(existingType, Translations);
                }
                else //Update
                {
                    if (UpdateProperty(sourceProperty, existingProperty))
                    {
                        AddLog(String.Format("La propriété ({0}/{1}) du type ({2}/{3}) a été modifiée.",
                            sourceProperty.Id, sourceProperty.Name, existingType.ID, existingType.Name));

                        hasChanged = true;
                    }
                }
            }

            //Suppression
            var propsToRemove = existingType.PropertiesInternal.Where(existingProperty =>
                !source.Properties.Any(sourceProperty =>
                    sourceProperty.Id.Equals(existingProperty.ID))).ToList();

            if (propsToRemove.Count() > 0)
            {
                hasChanged = true;

                foreach (var property in propsToRemove)
                {
                    AddLog(String.Format("La propriété ({0}/{1}) a été ajoutée au type ({2}/{3}).",
                    property.ID, property.Name, existingType.ID, existingType.Name));

                    existingType.RemoveProperty(property.ID);
                }
            }

            Translations.Add(source.LocalizedNames);

            if (hasChanged)
                existingType.Status = EntityStates.Dirty;

            return hasChanged;
        }

        private bool UpdateProperty(RecordTypeProperty sourceProperty, NodeTypeProperty existingProperty)
        {
            bool hasChanged = false;

            if (existingProperty.Name != sourceProperty.Name)
            {
                existingProperty.Name = sourceProperty.Name;
                hasChanged = true;
            }

            if (existingProperty.Description != sourceProperty.Description)
            {
                existingProperty.Description = sourceProperty.Description;
                hasChanged = true;
            }

            if (existingProperty.HaveTooltip != sourceProperty.HasTooltip)
            {
                existingProperty.HaveTooltip = sourceProperty.HasTooltip;
                hasChanged = true;
            }

            if (existingProperty.IsLanguageNeutral != sourceProperty.IsLanguageNeutral)
            {
                existingProperty.IsLanguageNeutral = sourceProperty.IsLanguageNeutral;
                hasChanged = true;
            }

            if (existingProperty.IsRequired != sourceProperty.IsRequired)
            {
                existingProperty.IsRequired = sourceProperty.IsRequired;
                hasChanged = true;
            }

            if (existingProperty.IsUnique != sourceProperty.ValueMustBeUnique)
            {
                existingProperty.IsUnique = sourceProperty.ValueMustBeUnique;
                hasChanged = true;
            }

            if ((existingProperty.ValidationRegex == null &&
                !String.IsNullOrEmpty(sourceProperty.RegexValidation)) ||
                    (existingProperty.ValidationRegex != null &&
                    existingProperty.ValidationRegex.ToString() !=
                    sourceProperty.RegexValidation))
            {
                existingProperty.ValidationRegex = new Regex(sourceProperty.RegexValidation);
                hasChanged = true;
            }

            if (existingProperty.GroupName != sourceProperty.GroupName)
            {
                existingProperty.GroupName = sourceProperty.GroupName;
                hasChanged = true;
            }

            Unit? medianamikControlWidth = null;
            if (!String.IsNullOrEmpty(sourceProperty.MedianamikControlWidth))
                medianamikControlWidth = new Unit(sourceProperty.MedianamikControlWidth);

            //TODO: many-to-many
            //    if (existingProperty.PrimaryMedianamikControlID != sourceProperty.MedianamikControlId)
            //    {
            //        existingProperty.PrimaryMedianamikControlID = sourceProperty.MedianamikControlId;
            //        hasChanged = true;
            //    }

            //    if (existingProperty.PrimaryMedianamikControlInfo.Height !=
            //        sourceProperty.MedianamikControlHeight ||
            //        existingProperty.PrimaryMedianamikControlInfo.Width != medianamikControlWidth)
            //    {
            //        existingProperty.PrimaryMedianamikControlInfo =
            //            new MedianamikControlInfo(medianamikControlWidth,
            //                                      sourceProperty.MedianamikControlHeight);
            //        hasChanged = true;
            //    }

            if (sourceProperty.IsLookup)
            {
                if (existingProperty.PrimaryTypeID != sourceProperty.PrimaryTypeId)
                {
                    existingProperty.PrimaryTypeID = sourceProperty.PrimaryTypeId;
                    hasChanged = true;
                }

                if (existingProperty.PrimaryPropertyID != sourceProperty.PrimaryPropertyId)
                {
                    existingProperty.PrimaryPropertyID = sourceProperty.PrimaryPropertyId;
                    hasChanged = true;
                }
            }

            if (existingProperty.MedianamikControlID != sourceProperty.MedianamikControlId)
            {
                existingProperty.MedianamikControlID = sourceProperty.MedianamikControlId;
                hasChanged = true;
            }

            if (existingProperty.MedianamikControlInfo.Height != sourceProperty.MedianamikControlHeight ||
                existingProperty.MedianamikControlInfo.Width != medianamikControlWidth ||
                existingProperty.MedianamikControlInfo.TreeRootNodeID != sourceProperty.RootNodeId)
            {
                existingProperty.MedianamikControlInfo = new MedianamikControlInfo(medianamikControlWidth,
                                                                                   sourceProperty.MedianamikControlHeight,
                                                                                   sourceProperty.RootNodeId);
                hasChanged = true;
            }

            var roles = new RoleCollection(sourceProperty.Roles);
            if (!existingProperty.Roles.SequenceEqual(roles))
            {
                existingProperty.Roles.Clear();
                existingProperty.Roles.AddRange(new RoleCollection(sourceProperty.Roles));
                hasChanged = true;
            }


            if (existingProperty.MaxLength != sourceProperty.MaxLength)
            {
                existingProperty.MaxLength = sourceProperty.MaxLength;
                hasChanged = true;
            }

            if (existingProperty.MinLength != sourceProperty.MinLength)
            {
                existingProperty.MinLength = sourceProperty.MinLength;
                hasChanged = true;
            }

            if (existingProperty.Indexable != sourceProperty.IsIndexable)
            {
                existingProperty.Indexable = sourceProperty.IsIndexable;
                hasChanged = true;
            }

            Translations.Add(sourceProperty.LocalizedNames);

            return hasChanged;
        }

        /// La validation doit appeller AddException pour signifier une exception (accumulateur d'exceptions)

        protected override void Validate(RecordType source, NodeType existingType)
        {
            if (new Regex("[A-Z][a-zA-Z]*").IsMatch(source.Name))
            {
                bool isEdition = existingType != null;

                if ((isEdition && existingType.Name != source.Name &&
                          NodeTypeManager.NodeTypeExists(source.Name))
                          || (!isEdition && NodeTypeManager.NodeTypeExists(source.Name)))
                    AddException(String.Format("Le nom du type ({0}/{1}) est déjà utilisé.",
                        source.Id, source.Name));
            }
            else
            {
                AddException(String.Format("Le nom du type ({0}/{1}) n'est pas valide.",
                    source.Id, source.Name));
            }


            //TODO:Ajouter autres validations
        }

        protected override void ValidateSatelliteData(NodeType result)
        {
            //Validations de base
            base.ValidateSatelliteData(result);

            //LegitimateParentRecordIds / LegitimateParentRecordTypeIds / Templates
            //Ne pas générer d'exceptions s'ils n'existent pas dans la BD de destination

            //LegitimateParentRecordTypes
            var originalLegitimateParentTypes = result.OriginalLegitimateParentTypes.ToList();
            foreach (var legitimateParentType in originalLegitimateParentTypes
                .Where(l => !l.ID.Equals(Guid.Empty)))
            {
                if (FindExisting(legitimateParentType.ID) == null)
                {
                    AddWarning(String.Format("Le type ({0}) référencé par les LegitimateParentTypes du type ({1}/{2}) n'existe pas.",
                        legitimateParentType.ID, result.ID, result.Name));
                    result.OriginalLegitimateParentTypes.RemoveAll(t => t.ID.Equals(legitimateParentType.ID));
                }
            }

            //LegitimateParentNodes
            var originalLegitimateParentNodes = result.OriginalLegitimateParentNodes.ToList();
            foreach (var legitimateParentNode in originalLegitimateParentNodes)
            {
                if (FindExistingNode(legitimateParentNode.ID) == null)
                {
                    AddWarning(String.Format("Le noeud ({0}) référencé par les LegitimateParentTypes du type ({1}/{2}) n'existe pas.",
                        legitimateParentNode.ID, result.ID, result.Name));
                    result.OriginalLegitimateParentTypes.RemoveAll(t => t.ID.Equals(legitimateParentNode.ID));
                }
            }

            //Templates
            var templates = result.Templates.ToList();
            foreach (var template in templates)
            {
                if (FindExistingNode(template.WebSiteId) == null)
                {
                    result.Templates.RemoveAll(t => t.WebSiteId.Equals(template.WebSiteId));
                    AddWarning(String.Format("Le website ({2}) référencé par le TypeTemplate du type ({0}/{1}) n'existe pas.",
                                      result.ID, result.Name, template.WebSiteId));
                }

                if (!AllPageTemplates.Any(t => t.ID.Equals(template.TemplateId)))
                {
                    result.Templates.RemoveAll(t => t.TemplateId.Equals(template.TemplateId));
                    AddWarning(String.Format("Le PageTemplate ({2}) référencé par le template du type ({0}/{1}) n'existe pas.",
                                      result.ID, result.Name, template.TemplateId));
                }
            }

            //Propriétés
            foreach (var property in result.Properties)
            {
                //DataType
                if (!property.DataTypeId.Equals(DataTypes.UniqueIdentifier) && !AllDataTypes.Any(t => t.ID.Equals(property.DataTypeId)))
                    AddException(String.Format("Le DataType ({2}) référencé par la propriété ({3}/{4}) du type ({0}/{1}) n'existe pas.",
                                        result.ID, result.Name, property.DataTypeId, property.ID, property.Name));

                //Lookup -- Plutot many-to-many (pas supporté actuellement)
                //if (property.IsLookup)
                //{
                //    //PrimaryType
                //    var primaryType = FindExisting(property.PrimaryTypeID.Value);
                //    if (primaryType == null)
                //        AddException(String.Format("Le PrimaryType ({0}) référencé par la propriété ({1}/{2}) du type ({3}/{4}) n'existe pas.",
                //                       property.PrimaryTypeID.Value, property.ID, property.Name, result.ID, result.Name));

                //    //PrimaryProperty
                //    if (property.PrimaryPropertyID.HasValue)
                //        if (!primaryType.GetAllProperties().Any(prop => prop.ID.Equals(property.PrimaryPropertyID.Value)))
                //            AddException(String.Format("Le PrimaryProperty ({0}) référencé par la propriété ({1}/{2}) du type ({3}/{4}) n'existe pas.",
                //                           property.PrimaryPropertyID.Value, property.ID, property.Name, result.ID, result.Name));

                //    //Many-To-Many
                //    if (property.PrimaryMedianamikControlID.HasValue)
                //    {
                //        //PrimaryMedianamikControl
                //        if (!AllControls.Any(t => t.ID.Equals(property.PrimaryMedianamikControlID.Value)))
                //            AddException(String.Format("Le MedianamikControl (primary) ({0}) référencé par la propriété ({1}/{2}) du type ({3}/{4}) n'existe pas.",
                //                        property.PrimaryMedianamikControlID.Value, property.ID, property.Name, result.ID, result.Name));
                //    }
                //}
                //else
                //{

                //MedianamikControl
                if (property.MedianamikControlID.HasValue && !AllControls.Any(t => t.ID.Equals(property.MedianamikControlID.Value)))
                    AddException(String.Format("Le MedianamikControl ({0}) référencé par la propriété ({1}/{2}) du type ({3}/{4}) n'existe pas.",
                            property.MedianamikControlID.Value, property.ID, property.Name, result.ID,
                            result.Name));

                //}
            }
        }

        protected HashSet<Node> NodeBag { get { return _nodeBag; } }
        HashSet<Node> _nodeBag = new HashSet<Node>();

        protected static IEnumerable<PageTemplate> AllPageTemplates = ConfigManager.GetAll<PageTemplate>();
        protected static ReadOnlyCollection<DataType> AllDataTypes = ConfigManager.GetAllDataTypes();
        protected static Collection<MedianamikControl> AllControls = ConfigManager.GetMedianamikControls();

        private Node FindExistingNode(Guid id)
        {
            var result = NodeBag.FirstOrDefault(t => t.ID.Equals(id));

            if (result == null)
            {
                result = NodeManager.GetNode(id);

                if (result != null)
                    NodeBag.Add(result);
            }

            return result;
        }

        private HashSet<NodeType> TypesToRegenerate = new HashSet<NodeType>();
        private HashSet<NodeType> TypesToClean = new HashSet<NodeType>();
        private HashSet<Translation> Translations = new HashSet<Translation>();
    }
}
